PID Control for self-driving
Udacity Self-Driving Engineer Nanodegree. Term 2, assignment 3
- Build a PID controller and tune the PID hyperparameters by applying the general processing flow as described in the previous lessons.
- Test the solution on Udacity simulator.
- See how fast you get the vehicle to SAFELY go!
The simulator data
Udacity’s simulator app provides cross-track error
CTE, speed, and steering angle data via local websocket. With the continuous stream of data the PID controller responds with steering and throttle commands driving the car reliably around the simulator track.
The effect of the P, I and D components
A proportional–integral–derivative controller is a control loop feedback mechanism widely used in industrial control systems and a variety of other applications requiring continuously modulated control. A PID controller continuously calculates an error value as the difference between a desired setpoint (SP) and a measured process variable (PV) and applies a correction based on proportional, integral, and derivative terms (denoted P, I, and D respectively) which give the controller its name.
P os the component that is proportional to the current track error. It has direct impact on the trajectory because it makes the car to “correct” in the same proportion of the error in the opposite direction. There’s a natural overshooting effect that will cause the car to swivel hard left and right eventually driving the car off-track. See a video of the controller using only the proportional control.
It’s not hard to guess that larger values of
P will cause the car to oscillate faster.
A way to cancel the overshoot effect is to introduce a temporal derivative of the
CTE. The term
D is the best estimate of the future trend of the error, based on its current rate of change. When the car has turned enough to reduce the crosstrack error,
Dwill inform the controller that the error has already reduced.
As the error becomes smaller over time the counter steering won’t be as sharp helping the converge the movement to the target trajectory.
The final term
I considers all past values of the
CTE and it’s measured by the integral or the sum of the crosstrack errors over time. The reason we need it is that there’s likely residual error after applying the proportional control. This ends up causing a bias over a long period of time that avoids the car to get in the exact trajectory. This integral term seeks to eliminate this residual error by adding a historic cumulative value of the error.
Choosing the hyperparameters
Now the question is how to choose the optimal coefficient values for our PID controller. Picking a coefficient that increases or decreases too much
D can cause the car trajectory to go awfully wrong.
There are a few approaches to iterate over different values and get to optimal hyperparameters.
The twiddle algorithm, also known as “coordinate ascent” is a generic algorithm that tries to find a good choice of parameters for an algorithm that returns an error. It’s a good strategy for this case and it’s clearly explained by Sebastian Thrun’s lesson.
Another alternative would be to use stochastic gradient descent. In the Udacity forums I could find this interesting writeup of applying backpropagation to the specific assignment.
I spent a good time implementing both but at the end I decided to try manual iteration with different parameters and managed to get good enough results. A good strategy to iterate values manually can be described by this post:
1 - Set all gains to zero.
2 - Increase the P gain until the response to a disturbance is steady oscillation.
3 - Increase the D gain until the the oscillations go away (i.e. it’s critically damped).
4 - Repeat steps 2 and 3 until increasing the D gain does not stop the oscillations.
5 - Set P and D to the last stable values.
6 - Increase the I gain until it brings you to the setpoint with the number of oscillations desired
Besides of implementing a PID controller to define steering values for the car I also did the same for controlling the car speed. This way the program could try higher speeds than the default of 30mph.
I implemented a
switch statement in the C++ code to run multiple tests. It would apply some coefficients for a about 1,500 data points and then reset the simulator for the next test case.