Behavioral Cloning Tips and Tricks — Udacity CarND

Dat Nguyen
Feb 23, 2017 · 5 min read

Project : https://github.com/dat-ai/behavioral-cloning

My appoarch is to try to minimize the amount of parameters low while retaining the accuracy as high as possible. Many suggest to use NVIDIA End-to-End Learning For Self-Driving Cars since it is provenly well-suited for this problem. However, I want to explore something new.


Recurrent Neural Network works well on temporal input like video. I believe Recurrent Neural Network actually should be applied to this problem. In fact, the winner of the Udacity Challenge 2 used an LSTM + CNN to train his model. In theory, this should work better than Convolutional Neural Networks. However, it was hard to train due to exploding vanishing gradient or I might not know how to train it properly yet.

Due to time constraint, I decided to switch back to CNN. I found an interesting paper by He et all. Basically, they did not use traditional Conv--->ReLU-->MaxPool. Instead, they eliminated MaxPool and performed BatchNorm before Activation along with ResNet architecture. So it would be look like the following

Before fully connected layer, they did an average pooling. This significantly reduces the amount of parameters. I tried to implement it. Tadaa, it provided me much better result.

Here is my Network architecture.

https://gist.github.com/2d9501e8d1c964d1c06596054b81a081

2. Data Augmentation

2.1 OpenCV is wonderful

The goal of data augmentation is to assist the model generalize better. In this project, I re-used to image tools from project 2 which take an image and perform multiple transformations(blurring, rotation and changing brightness of an image).

def random_transform(img):
# There are total of 3 transformation
# I will create an boolean array of 3 elements [ 0 or 1]
a = np.random.randint(0, 2, [1, 3]).astype(‘bool’)[0]
if a[1] == 1:
img = rotate(img)
if a[2] == 1:
img = blur(img)
if a[3] == 1:
img = gamma(img)
return img

2.2 Flip that image!

You might found that during data collection you might be unconsciously biased toward one side of street. So flipping the image helps your model generalize better. As suggested by Udacity, driving in opposite direction also helps your model. The reason is the lap has too many left turns. By driving in reversed direction, you force your model to learn the right turn too.

# #############################
# ## DATA AUGMENTATION ########
###############################
from utils.image_processor import random_transform
augmented_images = []
augmented_measurements = []
for image, measurement in zip(images, measurements):
flipped_image = cv2.flip(image, 1)
augmented_images.append(flipped_image)
flipped_angle = measurement[0] * -1.0
augmented_measurements.append((flipped_angle, measurement[1], measurement[2]))
# #
rand_image = random_transform(image)
augmented_images.append(rand_image)
augmented_measurements.append(measurement)

3. Training strategies

In this particular project, training goal is to minimize the loss (mean root square errors) of the steering angle. In my labels, I had [steering_angle, throttle, speed](for my future RNN’s model), I had to write a custom loss function as following:

def mse_steer_angle(y_true, y_pred):
''' Custom loss function to minimize Loss for steering angle '''
return mean_squared_error(y_true[0], y_pred[0])

In order to use this custom loss function, I applied my loss during the compilation of my model:

# Compile model
model.compile(optimizer=Adam(lr=learn_rate), loss=[mse_steer_angle])

3.2 Be careful about high learning rate

Another issue is I initially set my training rate to 0.001 as many suggested. However, it did not work well for me. My loss kept fluctuating. So I decided to go lower learning rate 0.0001 and it worked. Therefore, if you see your loss fluctuates during the training process. It is a strong indication that the learning rate might be too high. Try to lower the learning rate, it might help.

3.1 Know when to stop

One of the mistakes I made during the training process was too focused on minimizing the loss for steering angle. What happened to me was, if I trained my model too long, it would drive very weird in one map (my training data included both maps). Therefore, my training strategy is to lower my learning rate and to use early stopping technique.Once my model worked as expected in the simulator, I stopped the training process. If you are not very satisfied to your result. Try use lower learning rate 0.00010.00001. Also, saving your model during every epochs could be helpful, too. Here is how I did it.

from keras.callbacks import ModelCheckpoint
....
checkpoint = ModelCheckpoint('checkpoints/weights.{epoch:02d}-{val_loss:.3f}.h5', save_weights_only=True)
...
model.fit_generator(data_augment_generator(images, measurements, batch_size=batch_size), samples_per_epoch=data_size, callbacks=[checkpoint], nb_val_samples=data_size * 0.2, nb_epoch=epochs)

4. From simulator to real RC racing car

Finally, I would like to advertise my current project Autonomous 1/10th Self-Racing Car . I applied what I learned from Behavioral Cloning into a real RC car. This a wonderful chance to validate my model in real track. My car is used NVIDIA Jetson TX2 as a brain to control the steering servo and ESC (You can used Raspberry Pi 3 but it could be slow).

5. Future goal, Recurrent Neural Network + CNN or Deep Reinforcement Learning

In the future, I would like to use recurrent neural network for this project. The reason is every change in this world is respected to time domain. Personally, it is not intuitive to use static images for data changing over time. Nevertheless, I am satisfied with my final result.